using System;
using EnvDTE;
using EnvDTE80;
using Extensibility;
using Microsoft.VisualStudio.CommandBars;
using System.Text;
using VSLangProj;
using System.Reflection;

namespace Nprove
{
	/// <summary>The object for implementing an Add-in.</summary>
	/// <seealso class='IDTExtensibility2' />
	public class Addin : IDTExtensibility2, IDTCommandTarget
	{
        private DTE2 _applicationObject;
        private AddIn _addInInstance;
        private OutputWindowPane _owp = null;
        const String OUTPUTWINDOWPANENAME = @"Nprove Add-in";
        private String _vb6Path;
        private String _msbuildPath;
        private String _projectExcludeFilter;

        // commands
        Command _addToSolutionCom;
        Command _buildProjectCom;
        Command _linkProjectCom;
        Command _fixProjectCom;

        /// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
		public Addin()
		{

		}

		/// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
		/// <param term='application'>Root object of the host application.</param>
		/// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
		/// <param term='addInInst'>Object representing this Add-in.</param>
		/// <seealso class='IDTExtensibility2' />
		public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
		{
            _applicationObject = (DTE2)application;
            _addInInstance = (AddIn)addInInst;

			if(connectMode == ext_ConnectMode.ext_cm_AfterStartup)
			{
                OnStartupComplete(ref custom);
			}
		}

        private void Initialise()
        {
            StringBuilder sb = new StringBuilder();
            try
            {
                DllSettings settings = new DllSettings();
                System.Configuration.SettingValueElement o = settings["ProjectExcludeFilter"] as System.Configuration.SettingValueElement;
                if (o == null)
                {
                    sb.AppendLine("Node not found: ProjectExcludeFilter");
                }
                else
                {
                    _projectExcludeFilter = o.ValueXml.InnerText;
                    sb.AppendLine("Filter is : " + _projectExcludeFilter);
                }
                o = settings["VB6"] as System.Configuration.SettingValueElement;
                if (o == null)
                {
                    sb.AppendLine("Node not found: VB6");
                }
                else
                {
                    _vb6Path = o.ValueXml.InnerText;
                    sb.AppendLine("VB6 : " + _vb6Path);
                }

                o = settings["MSBuild"] as System.Configuration.SettingValueElement;
                if (o == null)
                {
                    sb.AppendLine("Node not found: MSBuild");
                }
                else
                {
                    _msbuildPath = o.ValueXml.InnerText;
                    sb.AppendLine("Msbuild : " + _msbuildPath);
                }
            }
            catch (Exception ex)
            {
                sb.AppendLine(ex.Message);
                sb.AppendLine(ex.StackTrace);
            }
            finally
            {
                ShowMessage(sb);
            }
        }

        private void ShowMessage(StringBuilder sb)
        {
            if (_owp == null)
            {
                OutputWindowPanes owps = _applicationObject.ToolWindows.OutputWindow.OutputWindowPanes;
                for (int paneIndex = 1; paneIndex <= owps.Count; paneIndex++)
                {
                    _owp = owps.Item(paneIndex);
                    if (_owp.Name == OUTPUTWINDOWPANENAME) break;
                    _owp = null;
                }
                if (_owp == null)
                {
                    _owp = owps.Add(OUTPUTWINDOWPANENAME);
                }
            }

            if (_owp != null)
            {
            _owp.OutputString(sb.ToString());
            }     
        }

		/// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>
		/// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
		/// <param term='custom'>Array of parameters that are host application specific.</param>
		/// <seealso class='IDTExtensibility2' />
		public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
		{
		}

		/// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
		/// <param term='custom'>Array of parameters that are host application specific.</param>
		/// <seealso class='IDTExtensibility2' />		
		public void OnAddInsUpdate(ref Array custom)
		{
		}

		/// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
		/// <param term='custom'>Array of parameters that are host application specific.</param>
		/// <seealso class='IDTExtensibility2' />
		public void OnStartupComplete(ref Array custom)
		{
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("Current settings:");

            Initialise();

            ShowMessage(sb);

            sb.Clear();
            object[] contextGUIDS = new object[] { };
            Commands2 commands = (Commands2)_applicationObject.Commands;

            try
            {
                Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];

                CommandBarControl toolsControl = menuBarCommandBar.Controls[@"Tools"];
                CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;

                Microsoft.VisualStudio.CommandBars.CommandBar contextMenuCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["Team Foundation Context Menus"];
                CommandBarControl scContextMenuControl = contextMenuCommandBar.Controls["Source Control Explorer"];
                CommandBarPopup scContextMenuPopup = (CommandBarPopup)scContextMenuControl;

                try
                {
                    _addToSolutionCom = commands.AddNamedCommand2(_addInInstance, "AddToSolution", "Add to Solution",
                        "Adds select project to the current solution.",
                        true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
                        (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
                    if ((_addToSolutionCom != null) && (toolsPopup != null))
                    {
                        _addToSolutionCom.AddControl(toolsPopup.CommandBar, 1);
                    }

                    if ((_addToSolutionCom != null) && (scContextMenuPopup != null))
                    {
                        _addToSolutionCom.AddControl(scContextMenuPopup.CommandBar, 1);
                    }
                }
                catch (Exception ex)
                {
                    sb.AppendLine(ex.ToString());
                }

                try
                {
                    _buildProjectCom = commands.AddNamedCommand2(_addInInstance, "BuildProject",
                        "Build Project without loading it", "Builds a project in a separate window without loading it in the current solution.",
                        true, 69, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
                        (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

                    if ((_buildProjectCom != null) && (toolsPopup != null))
                    {
                        _buildProjectCom.AddControl(toolsPopup.CommandBar, 1);
                    }

                    if ((_buildProjectCom != null) && (scContextMenuPopup != null))
                    {
                        _buildProjectCom.AddControl(scContextMenuPopup.CommandBar, 1);
                    }
                }
                catch (Exception ex)
                {
                    sb.AppendLine(ex.ToString());
                }

                try
                {
                    _linkProjectCom = commands.AddNamedCommand2(_addInInstance, "LinkProject",
                        "Refresh dependencies in Solution", "Use this option to rebuild the dependencies among the loaded projects.",
                        true, 190, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
                        (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

                    if ((_linkProjectCom != null) && (toolsPopup != null))
                    {
                        _linkProjectCom.AddControl(toolsPopup.CommandBar, 1);
                    }
                }
                catch (Exception ex)
                {
                    sb.AppendLine(ex.ToString());
                }

                try
                {
                    _fixProjectCom = commands.AddNamedCommand2(_addInInstance, "FixProject",
                        "Fix project references in Solution", "Use this option to remove the copy local of the references in all projects.",
                        true, 0, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled,
                        (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

                    if ((_fixProjectCom != null) && (toolsPopup != null))
                    {
                        _fixProjectCom.AddControl(toolsPopup.CommandBar, 1);
                    }
                }
                catch (Exception ex)
                {
                    sb.AppendLine(ex.ToString());
                }

            }
            catch (System.ArgumentException ex)
            {
                sb.AppendLine(ex.ToString());
            }
            finally
            {
                ShowMessage(sb);
            }
		}

		/// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
		/// <param term='custom'>Array of parameters that are host application specific.</param>
		/// <seealso class='IDTExtensibility2' />
		public void OnBeginShutdown(ref Array custom)
		{
		}
		
		/// <summary>Implements the QueryStatus method of the IDTCommandTarget interface. This is called when the command's availability is updated</summary>
		/// <param term='commandName'>The name of the command to determine state for.</param>
		/// <param term='neededText'>Text that is needed for the command.</param>
		/// <param term='status'>The state of the command in the user interface.</param>
		/// <param term='commandText'>Text requested by the neededText parameter.</param>
		/// <seealso class='Exec' />
		public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
		{
			if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
			{
                if (commandName == "Nprove.Addin.AddToSolution")
				{
					status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
					return;
				}

                if (commandName == "Nprove.Addin.BuildProject")
                {
                    status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
                    return;
                }

                if (commandName == "Nprove.Addin.LinkProject")
                {
                    status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
                    return;
                }

                if (commandName == "Nprove.Addin.FixProject")
                {
                    status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
                    return;
                }
            }
		}

		/// <summary>Implements the Exec method of the IDTCommandTarget interface. This is called when the command is invoked.</summary>
		/// <param term='commandName'>The name of the command to execute.</param>
		/// <param term='executeOption'>Describes how the command should be run.</param>
		/// <param term='varIn'>Parameters passed from the caller to the command handler.</param>
		/// <param term='varOut'>Parameters passed from the command handler to the caller.</param>
		/// <param term='handled'>Informs the caller if the command was handled or not.</param>
		/// <seealso class='Exec' />
		public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
		{
			handled = false;
			if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
			{
                if (commandName == "Nprove.Addin.AddToSolution")
				{
                    handled = AddProjectsInSelectionToSolution();
                }
                else
                if (commandName == "Nprove.Addin.BuildProject")
                {
                    handled = BuildProjects();
                }
                else
                if (commandName == "Nprove.Addin.LinkProject")
                {
                    handled = LinkProjects();
                }
                else
                if (commandName == "Nprove.Addin.FixProject")
                {
                    handled = FixProjects();
                }

			}
		}

        private bool LinkProjects()
		{
            // delete all dependencies before starting to create new ones.
            for (int p = 1; p <= _applicationObject.Solution.Projects.Count; p++)
            {
                Project project = _applicationObject.Solution.Projects.Item(p);
                BuildDependency dependencies = FindBuildDependencies(project);
                if (dependencies == null) continue;
                dependencies.RemoveAllProjects();
            }

            // Create new dependencies based in references to binaries
            for (int p = 1; p <= _applicationObject.Solution.Projects.Count; p++ )
            {
                LinkProject(_applicationObject.Solution.Projects.Item(p));
            }
            return true;
		}

        private void LinkProject(Project project)
        {
            BuildDependency dependencies = FindBuildDependencies(project);
            if (dependencies == null) return;

            VSProject vsp = project.Object as VSProject;
            References references = vsp.References;
            for (int r = 1; r <= references.Count; r++)
            {
                Reference reference = references.Item(r);

                Project referenced = FindProject(reference.Name);
                if (referenced == null) continue;
                dependencies.AddProject(referenced.UniqueName);
            }
        }

        private BuildDependency FindBuildDependencies(Project project)
        {
            BuildDependency dependency = null;
            for (int d = 1; d <= _applicationObject.Solution.SolutionBuild.BuildDependencies.Count; d++)
            {
                dependency = _applicationObject.Solution.SolutionBuild.BuildDependencies.Item(d);
                if (dependency.Project == project)
                {
                    return dependency;
                }
            }
            return null;
        }

        private Project FindProject(string name)
        {
            for (int p = 1; p <= _applicationObject.Solution.Projects.Count; p++)
            {
                Project project = _applicationObject.Solution.Projects.Item(p);
                if (project.Properties == null) continue;

                Property assembly = project.Properties.Item("AssemblyName");
                if (String.Equals(assembly.Value as String, name, StringComparison.InvariantCultureIgnoreCase ))
                {
                    return project;
                }
            }
            return null;
        }


        private bool BuildProjects()
        {
            bool handled = false;
            try
            {
                Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt vc =
                    _applicationObject.GetObject("Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt")
                    as Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt;

                foreach (Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExplorerItem item in vc.Explorer.SelectedItems)
                {
                    if (item.IsFolder)
                    {
                        //AddProjectsInFolder(item);
                    }
                    else
                    {
                        BuildProject(item);
                    }
                }
                handled = true;

            }
            catch (Exception)
            {
            }
            return handled;
            
        }

        private void BuildProject(Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExplorerItem item)
        {
            String projectPath = item.LocalPath;
            if (String.IsNullOrEmpty(projectPath)) return;
 
            String extension = System.IO.Path.GetExtension(projectPath);
            if (extension.Equals(@".csproj", StringComparison.CurrentCultureIgnoreCase) || extension.Equals(@".vbproj", StringComparison.CurrentCultureIgnoreCase))
            {
                new System.Threading.Thread(
                    delegate()
                    {
                        StringBuilder sb = new StringBuilder();
                        try
                        {
                            System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
                            psi.FileName = _msbuildPath;
                            psi.Arguments = projectPath;
                            psi.CreateNoWindow = true;
                            psi.RedirectStandardInput = true;
                            psi.RedirectStandardOutput = true;
                            psi.RedirectStandardError = true;
                            psi.UseShellExecute = false;
                            System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
                            sb.AppendLine (p.StandardOutput.ReadToEnd());
                            sb.AppendLine (p.StandardError.ReadToEnd());
                            ShowMessage(sb);
                        }
                        catch (Exception ex)
                        {
                            sb.AppendLine(ex.Message);
                            ShowMessage(sb);
                        }
                    }
                ).Start();
            }
            else 
            if (extension.Equals(@".vbp", StringComparison.CurrentCultureIgnoreCase))
            {
                new System.Threading.Thread(
                    delegate()
                    {
                        StringBuilder sb = new StringBuilder();
                        try
                        {
                            System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
                            psi.FileName = _vb6Path;
                            psi.Arguments = "\"" + projectPath + "\"";
                            psi.CreateNoWindow = true;
                            psi.RedirectStandardInput = true;
                            psi.RedirectStandardOutput = true;
                            psi.RedirectStandardError = true;
                            psi.UseShellExecute = false;
                            psi.Arguments = @"/make /out " + "\"" + projectPath + ".log\" \"" + projectPath + "\"";
                            System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
                            
                            sb.AppendLine(p.StandardOutput.ReadToEnd());
                            sb.AppendLine(p.StandardError.ReadToEnd());
                            if (System.IO.File.Exists(projectPath + ".log"))
                            {
                                sb.AppendLine(System.IO.File.ReadAllText(projectPath + ".log"));
                                System.IO.File.Delete(projectPath + ".log");
                            }
                            ShowMessage(sb);
                        }
                        catch (Exception ex)
                        {
                            sb.AppendLine(ex.Message);
                            ShowMessage(sb);
                        }
                    }
                ).Start();
            }
        }

        private bool AddProjectsInSelectionToSolution()
        {
            StringBuilder sb = new StringBuilder();
            bool handled = false;

            try
            {
                Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt vc =
                    _applicationObject.GetObject("Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt")
                    as Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt;

                foreach (Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExplorerItem item in vc.Explorer.SelectedItems)
                {
                    if (item.IsFolder)
                    {
                        StringBuilder sbx = AddProjectsInFolder(item);
                        sb.Append(sbx.ToString());
                    }
                    else
                    {
                        sb.AppendLine (AddProject(item));
                    }
                }
                handled = true;

            }
            catch (Exception ex)
            {
                sb.AppendLine(ex.ToString());
            }

            ShowMessage(sb);

            return handled;
        }

        private StringBuilder AddProjectsInFolder(Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExplorerItem item)
        {
            StringBuilder sb = new StringBuilder();
            String []items = System.IO.Directory.GetFiles(item.LocalPath, "*.*", System.IO.SearchOption.AllDirectories);
            foreach(String file in items)
            {
                String extension = System.IO.Path.GetExtension(file);
                if (
                    (extension.Equals(@".csproj", StringComparison.CurrentCultureIgnoreCase) || extension.Equals(@".vbproj", StringComparison.CurrentCultureIgnoreCase))
                    &&
                    IsFileInExcludedFilter(file))
                {
                    try
                    {
                        Project project = _applicationObject.Solution.AddFromFile(file, false);
                        LinkProject(project);
                    }
                    catch (Exception ex)
                    {
                        sb.AppendLine(ex.ToString());
                    }
                }
            }

            return sb;
        }

        private bool IsFileInExcludedFilter(string file)
        {
            String [] filters = _projectExcludeFilter.Split(@",".ToCharArray());

            foreach (String filter in filters)
            {
                if (file.IndexOf(filter) > -1) return false;
            }

            return true;
        }

        private String AddProject(Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExplorerItem item)
        {
            String result = String.Empty;
            String projectPath = item.LocalPath;
            String extension = System.IO.Path.GetExtension(projectPath);
            if (extension.Equals(@".csproj", StringComparison.CurrentCultureIgnoreCase) || extension.Equals(@".vbproj", StringComparison.CurrentCultureIgnoreCase))
            {
                try
                {
                    Project project = _applicationObject.Solution.AddFromFile(projectPath, false);
                    LinkProject(project);
                    result = @"Adding to solution:" + projectPath;
                }
                catch (Exception ex)
                {
                    result = ex.Message;
                }
            }

            return result;
        }

        private bool FixProjects()
        {
            StringBuilder sb = new StringBuilder ();
            for (int p = 1; p <= _applicationObject.Solution.Projects.Count; p++)
            {
                FixProject(_applicationObject.Solution.Projects.Item(p), sb);
            }

            ShowMessage(sb);

            return true;
        }

        private void FixProject(Project project, StringBuilder sb)
        {
            VSProject vsp = project.Object as VSProject;
            References references = vsp.References;
            for (int r = 1; r <= references.Count; r++)
            {
                Reference reference = references.Item(r);
                if (reference.CopyLocal)
                {
                    reference.CopyLocal = false;
                    sb.AppendFormat(" Fixing reference: {0} in project {1}", reference.Name, project.Name);
                    sb.AppendLine();
                }
            }
        }
	}
}